/*
* Copyright (C)
* Copyright (C)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "AccountMgr.h"
#include "DatabaseEnv.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "Util.h"
#include "SHA1.h"
#include "WorldSession.h"

namespace AccountMgr
{

	AccountOpResult CreateAccount(std::string username, std::string password)
	{
		if (utf8length(username) > MAX_ACCOUNT_STR)
			return AOR_NAME_TOO_LONG;                           // username's too long

		normalizeString(username);
		normalizeString(password);

		if (GetId(username))
			return AOR_NAME_ALREDY_EXIST;                       // username does already exist

		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT);

		stmt->setString(0, username);
		stmt->setString(1, CalculateShaPassHash(username, password));

		LoginDatabase.Execute(stmt);

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_REALM_CHARACTERS_INIT);

		LoginDatabase.Execute(stmt);

		return AOR_OK;                                          // everything's fine
	}

	AccountOpResult DeleteAccount(uint32 accountId)
	{
		// Check if accounts exists
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_ID);
		stmt->setUInt32(0, accountId);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		if (!result)
			return AOR_NAME_NOT_EXIST;

		// Obtain accounts characters
		stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID);

		stmt->setUInt32(0, accountId);

		result = CharacterDatabase.Query(stmt);

		if (result)
		{
			do
			{
				uint32 guidLow = (*result)[0].GetUInt32();
				uint64 guid = MAKE_NEW_GUID(guidLow, 0, HIGHGUID_PLAYER);

				// Kick if player is online
				if (Player* p = ObjectAccessor::FindPlayer(guid))
				{
					WorldSession* s = p->GetSession();
					s->KickPlayer();                            // mark session to remove at next session list update
					s->LogoutPlayer(false);                     // logout player without waiting next session list update
				}

				Player::DeleteFromDB(guid, accountId, false, true);       // no need to update realm characters
			} while (result->NextRow());
		}

		// table realm specific but common for all characters of account for realm
		stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TUTORIALS);
		stmt->setUInt32(0, accountId);
		CharacterDatabase.Execute(stmt);

		stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_DATA);
		stmt->setUInt32(0, accountId);
		CharacterDatabase.Execute(stmt);

		stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_BAN);
		stmt->setUInt32(0, accountId);
		CharacterDatabase.Execute(stmt);

		SQLTransaction trans = LoginDatabase.BeginTransaction();

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT);
		stmt->setUInt32(0, accountId);
		trans->Append(stmt);

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_ACCESS);
		stmt->setUInt32(0, accountId);
		trans->Append(stmt);

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_REALM_CHARACTERS);
		stmt->setUInt32(0, accountId);
		trans->Append(stmt);

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_BANNED);
		stmt->setUInt32(0, accountId);
		trans->Append(stmt);

		LoginDatabase.CommitTransaction(trans);

		return AOR_OK;
	}


	AccountOpResult ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword)
	{
		// Check if accounts exists
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_ID);
		stmt->setUInt32(0, accountId);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		if (!result)
			return AOR_NAME_NOT_EXIST;

		if (utf8length(newUsername) > MAX_ACCOUNT_STR)
			return AOR_NAME_TOO_LONG;

		if (utf8length(newPassword) > MAX_ACCOUNT_STR)
			return AOR_PASS_TOO_LONG;

		normalizeString(newUsername);
		normalizeString(newPassword);

		stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_USERNAME);

		stmt->setString(0, newUsername);
		stmt->setString(1, CalculateShaPassHash(newUsername, newPassword));
		stmt->setUInt32(2, accountId);

		LoginDatabase.Execute(stmt);

		return AOR_OK;
	}

	AccountOpResult ChangePassword(uint32 accountId, std::string newPassword)
	{
		std::string username;

		if (!GetName(accountId, username))
			return AOR_NAME_NOT_EXIST;                          // account doesn't exist

		if (utf8length(newPassword) > MAX_ACCOUNT_STR)
			return AOR_PASS_TOO_LONG;

		normalizeString(username);
		normalizeString(newPassword);

		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_PASSWORD);

		stmt->setString(0, CalculateShaPassHash(username, newPassword));
		stmt->setUInt32(1, accountId);

		LoginDatabase.Execute(stmt);

		return AOR_OK;
	}

	uint32 GetId(std::string const& username)
	{
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME);
		stmt->setString(0, username);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		return (result) ? (*result)[0].GetUInt32() : 0;
	}

	uint32 GetSecurity(uint32 accountId)
	{
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL);
		stmt->setUInt32(0, accountId);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		return (result) ? (*result)[0].GetUInt8() : uint32(SEC_PLAYER);
	}

	uint32 GetSecurity(uint32 accountId, int32 realmId)
	{
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_GMLEVEL_BY_REALMID);
		stmt->setUInt32(0, accountId);
		stmt->setInt32(1, realmId);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		return (result) ? (*result)[0].GetUInt8() : uint32(SEC_PLAYER);
	}

	bool GetName(uint32 accountId, std::string& name)
	{
		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_USERNAME_BY_ID);
		stmt->setUInt32(0, accountId);
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		if (result)
		{
			name = (*result)[0].GetString();
			return true;
		}

		return false;
	}

	bool CheckPassword(uint32 accountId, std::string password)
	{
		std::string username;

		if (!GetName(accountId, username))
			return false;

		normalizeString(username);
		normalizeString(password);

		PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD);
		stmt->setUInt32(0, accountId);
		stmt->setString(1, CalculateShaPassHash(username, password));
		PreparedQueryResult result = LoginDatabase.Query(stmt);

		return (result) ? true : false;
	}

	uint32 GetCharactersCount(uint32 accountId)
	{
		// check character count
		PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS);
		stmt->setUInt32(0, accountId);
		PreparedQueryResult result = CharacterDatabase.Query(stmt);

		return (result) ? (*result)[0].GetUInt64() : 0;
	}

	bool normalizeString(std::string& utf8String)
	{
		wchar_t buffer[MAX_ACCOUNT_STR + 1];

		size_t maxLength = MAX_ACCOUNT_STR;
		if (!Utf8toWStr(utf8String, buffer, maxLength))
			return false;
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#endif
		std::transform(&buffer[0], buffer + maxLength, &buffer[0], wcharToUpperOnlyLatin);
#ifdef _MSC_VER
#pragma warning(default: 4996)
#endif

		return WStrToUtf8(buffer, maxLength, utf8String);
	}

	std::string CalculateShaPassHash(std::string const& name, std::string const& password)
	{
		SHA1Hash sha;
		sha.Initialize();
		sha.UpdateData(name);
		sha.UpdateData(":");
		sha.UpdateData(password);
		sha.Finalize();

		return ByteArrayToHexStr(sha.GetDigest(), sha.GetLength());
	}

	bool IsPlayerAccount(uint32 gmlevel)
	{
		return gmlevel == SEC_PLAYER;
	}

	bool IsGMAccount(uint32 gmlevel)
	{
		return gmlevel >= SEC_GAMEMASTER && gmlevel <= SEC_CONSOLE;
	}

	bool IsAdminAccount(uint32 gmlevel)
	{
		return gmlevel >= SEC_ADMINISTRATOR && gmlevel <= SEC_CONSOLE;
	}

	bool IsConsoleAccount(uint32 gmlevel)
	{
		return gmlevel == SEC_CONSOLE;
	}

} // Namespace AccountMgr